package execution;


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Vector;

import com.developpez.adiguba.shell.ProcessConsumer;
import com.developpez.adiguba.shell.Shell;
import ihm.ProgressionTests;


/**
 * Permet d'exécuter le vecteur de cas de tests 
 * Met à jour l'interface graphique intermediaire ProgressionTests au fur et à mesure que 
 * les tests s'executent
 * Instancie un vecteur d'observations relatif à l'ensemble des cas de tests exécutés 
 * 
 */
public class ExecutionCode {
	private Vector<CasDeTest> campagnes ;
	private Vector<Observation> obsVect;
	private ProgressionTests progressionTests;

	/**
	 * Constructeur
	 * @param _campagnes
	 * @param progressionTests
	 */
	public ExecutionCode(Vector<CasDeTest> _campagnes,ProgressionTests progressionTests){
		campagnes = _campagnes;
		obsVect = new Vector<Observation>();
		this.progressionTests = progressionTests;
	}


	/**
	 * Execute une campagne de tests
	 */
	public void executerCampagne() {
		Thread thread = new Thread(new Runnable() {
			//
			@Override
			public void run() {
				// TODO Auto-generated method stub
				for(int i=0;i<campagnes.size();i++){
					try {
						executerTest(campagnes.get(i));

					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}	

				}
				// A la fin de l'execution de la campagne de tests, m à j de l'interface progression tests
				progressionTests.setObservationVect(obsVect);
				progressionTests.getAffichage().setEnabled(true);
			}
		});
		thread.start();

	}


	/**
	 *  Execute un cas de test
	 * @param _cas
	 * @throws IOException
	 * @throws InterruptedException
	 */
	private void executerTest(CasDeTest _cas) throws IOException, InterruptedException{

		Shell sh_compile,sh_run;
		ProcessConsumer pc_compile,pc_run;

		// crée le fichier batch pour la création de l'éxécutable
		creerInFichierCompilation(_cas.getType(),_cas.getChemin());

		// Exécution des commandes nécessaire pour la créations de l'éxécutable
		// Récupération des sorties standard et d'erreur
		sh_compile = new Shell ();
		pc_compile = sh_compile.shell();		
		pc_compile.input(new FileInputStream("in.txt"));
		pc_compile.output(new FileOutputStream("out.txt")) ;
		pc_compile.error(new FileOutputStream("err_compile.txt")) ;
		pc_compile.consume();

		progressionTests.getTextArea()
		.append("------ Observations pour le cas :"+_cas.getIdCas()+"\n\n");


		// Récupération des observations au niveau Ocarina , GCC , LD
		Observation obs  = new Observation(_cas);
		RecupererErreur recup;
		Vector <String> recupVect;

		// Récupération des observations Ocarina
		if(_cas.isRecuperationOcarina()){

			recup = new RecupererErreurOcarina(new File("/tmp/ocarina.log"));
			recupVect = recup.retournerErreur(RecupererErreurOcarina.ERROR_ID_OCARINA);
			obs.setErreurOcarina(recupVect);
			progressionTests.getTextArea().append("Observations au niveau Ocarina : \n");
			if(obs.getErreurOcarina().size() == 0){
				progressionTests.getTextArea().append("Pas d'erreurs observées au niveau Ocarina\n\n");
			}
			else
			{
				for (int i=0; i < obs.getErreurOcarina().size();i++){
					progressionTests.getTextArea().append(obs.getErreurOcarina().get(i)+"\n");
				}
				progressionTests.getTextArea().append(" \n");
			}
		}
		// Récupération des observations GCC
		if(_cas.isRecuperationGCC()){

			recup = new RecupererErreurGCC(new File("err_compile.txt"));
			recupVect = recup.retournerErreur(RecupererErreurGCC.ERROR_ID_GCC);
			obs.setErreurGCC(recupVect);
			progressionTests.getTextArea().append("Observations au niveau GCC :  \n");

			if(obs.getErreurGCC().size() == 0){
				progressionTests.getTextArea().append("Pas d'erreurs observées au niveau GCC\n\n");
			}
			else
			{
				for (int i=0; i < obs.getErreurGCC().size();i++){
					progressionTests.getTextArea().append(obs.getErreurGCC().get(i)+"\n");
				}
				progressionTests.getTextArea().append("\n");
			}
		}

		// Récupération des observations LD
		if(_cas.isRecuperationLD()){

			recup = new RecupererErreurLD(new File("err_compile.txt"));
			recupVect = recup.retournerErreur(RecupererErreurLD.ERROR_ID_LD);
			obs.setErreurLD(recupVect);
			progressionTests.getTextArea().append("Observations au niveau LD :  \n");
			if(obs.getErreurLD().size() == 0){
				progressionTests.getTextArea().append("Pas d'erreurs observées au niveau LD\n\n");
			}
			else
			{
				for (int i=0; i < obs.getErreurLD().size();i++){
					progressionTests.getTextArea().append(obs.getErreurLD().get(i)+"\n");
				}
				progressionTests.getTextArea().append("\n");
			}
		}				


		// teste si l'executable a bien été crée ou pas
		if(compilationVerif(_cas.getChemin()))
		{
			// crée le fichier batch pour le lancement de l'éxécutable
			creerInFichierExecution(_cas.getChemin());
			sh_run = new Shell();
			pc_run =sh_run.shell();
			pc_run.input(new FileInputStream("in.txt"));
			pc_run.output(new FileOutputStream("out.txt")) ;
			pc_run.error(new FileOutputStream("err_exec.txt")) ;

			// lance l'exécutable sur un nouveau Thread
			ExecutionCodeThread execCode = new ExecutionCodeThread(pc_run);
			execCode.start();

			// teste si dans le cas de tests il ya une variable à observer ou pas 
			if(_cas.isObserverVariable()){

				// lance gdb avec un fichier batch permettant de récupérer une variable
				ExecutionGDB execGDB = new ExecutionGDB(_cas.getChemin(),"avecObservation.txt",true);
				Thread.sleep(3000);
				execGDB.start();
			}
			else
			{
				// lance gdb avec un fichier batch permettant uniquement de démarrer l'execution
				ExecutionGDB execGDB = new ExecutionGDB(_cas.getChemin(),"sansObservation.txt",true);
				execGDB.start();

				Thread.sleep(10000);

				execGDB.interrupt();
				execCode.interrupt();

				// termine GDB : dans ce cas le batch file ne contient pas une ligne qui termine GDB
				finirGDB();
				finirQemu();


			}
			// termine Qemu


		}
		else
		{
			// Signale que l'éxécutable n'a pas été crée
			progressionTests.getTextArea().append("L'executable n'a pas été crée \n");
			progressionTests.getTextArea().append("Qemu ne peut pas être lancé pour ce cas de test \n\n");
		}


		// Récupération de la valeur de la variable observée

		if(_cas.isObserverVariable())
		{   


			if(compilationVerif(_cas.getChemin()))
			{
				// cherche la valeur de la variable observée dans le logfile de gdb
				String ValeurObserve = new EtatVariable()
				.observerVariable(_cas.getNumPartition(), _cas.getNomVariable(), _cas.getChemin()) ;
				obs.setValeurObserve(ValeurObserve);
				progressionTests.getTextArea().append("Variable observée : \n");
				progressionTests.getTextArea().append(_cas.getNomVariable()+ " = " + ValeurObserve+"\n");
				progressionTests.getTextArea().append("\n");


			}
			else
			{
				obs.setValeurObserve(null);
				// Signale que l'observation de la variable n'est pas possible
				progressionTests.getTextArea().append("Echec de l'observation de la variable: "+_cas.getNomVariable()+" \n\n");
			}
		}
		// rajoute les observations de ce cas au vecteur d'observations de la campagne
		obsVect.add(obs);
		// met à jour la barre de progression
		progressionTests.getProgression()
		.setValue(progressionTests.getProgression().getValue() + 1);

	}



	/**
	 * vérifie si l'étape de compilation s'est bien déroulé ou pas
	 * @param _chemin
	 * @return boolean
	 */
	private boolean compilationVerif(String _chemin){

		return new File(_chemin+"/generated-code/cpu/pok.elf").exists();
	}

	/**
	 * Crée un fichier contenant l'ensemble de commandes nécessaires pour créer l'exécutable.
	 * Selon la nature du cas de test on part soit d'un modèle AADL, soit d'un code généré à partir d'un modèle
	 * @param testType
	 * @param testChemin
	 * @throws IOException
	 */
	private void creerInFichierCompilation(int testType,String testChemin) throws IOException{

		String cmd = "make clean all -C "+testChemin;
		PrintWriter Writer =  new PrintWriter(new BufferedWriter (new FileWriter("in.txt")));

		Writer.println("export POK_PATH=/home/ise/pok");
		Writer.println("export PATH=/home/ise/ocarina-2.0/bin:$PATH");

		if(testType==CasDeTest.CODE){
			cmd = cmd + "/generated-code/cpu";
		}
		Writer.println(cmd);

		Writer.close();


	}


	/**
	 * Crée un fichier batch pour lancer l'exécutable sur Qemu
	 * @param testChemin
	 * @throws IOException
	 */
	private void creerInFichierExecution(String testChemin) throws IOException{
		String cmd = "make run -C "+testChemin+"/generated-code/cpu";

		PrintWriter Writer =  new PrintWriter(new BufferedWriter (new FileWriter("in.txt")));

		Writer.println("export POK_PATH=/home/ise/pok");
		Writer.println(cmd);

		Writer.close();

	}

	/**
	 * 	Tue le processus d'exécution de Qemu. 
	 *	Le PID est récupéré d'un fichier qemu.pid dans lequel l'identifiant du processus a été écrit lors du lancement de Qemu
	 * @throws IOException
	 */
	private void finirQemu() throws IOException{

		String pidfile = "/home/ise/pok/qemu.pid";
		BufferedReader br = new BufferedReader (new FileReader(new File(pidfile)));
		String pid = (br.readLine());
		Shell sh = new Shell();
		sh.command("kill "+pid+" 2>&1 > /dev/null").consume();


	}

	/**
	 * Tue le processus d'exécution de GDB
	 * @throws IOException
	 */
	private void finirGDB() throws IOException{
		Shell sh = new Shell();
		sh.command("kill $(ps -C gdb -o pid=) 2>&1 > /dev/null").consume();
	}

	/**
	 *  Thread chargé de lancer Qemu
	 * 
	 *
	 */
	class ExecutionCodeThread extends Thread{
		ProcessConsumer pc;
		public ExecutionCodeThread (ProcessConsumer _pc){
			pc = _pc;

		}
		public void run(){
			try {

				pc.consume();

			} catch (IOException e) {
				e.printStackTrace();
			}       
		}

	}


	/**
	 *  Thread chargé de lancer GDB pour piloter l'exécution sur Qemu
	 *
	 */
	class ExecutionGDB extends Thread{
		String chemin;
		String batchfile;
		boolean observe;
		Shell sh;

		public ExecutionGDB(String chemin,String batchfile,boolean observe){
			this.chemin = chemin;
			this.batchfile = batchfile;
			this.observe = observe;
		}

		public void run(){
			sh = new Shell();
			if(observe){
				try {
					sh.command("gdb -x "+batchfile+" " + chemin +"/generated-code/cpu/pok.elf > gdblogfile" ).consume();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			else
			{

				try {
					sh.command("gdb -x "+batchfile+" " + chemin +"/generated-code/cpu/pok.elf" ).consume();
				} catch (IOException e) {

					e.printStackTrace();
				}
			} 
		}

	}
}